/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_BASE_NUMBER_HELPER_H
#define ECMASCRIPT_BASE_NUMBER_HELPER_H

#include <cstdint>
#include "plugins/ecmascript/runtime/ecma_string.h"

#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "include/coretypes/tagged_value.h"

namespace ark::ecmascript::base {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static constexpr uint16_t SPACE_OR_LINE_TERMINAL[] = {
    0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004,
    0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F, 0x205F, 0x3000, 0xFEFF,
};

constexpr double MIN_RADIX = 2;
constexpr double MAX_RADIX = 36;
constexpr double MIN_FRACTION = 0;
constexpr double MAX_FRACTION = 100;

// Coversion flags
static constexpr uint32_t NO_FLAGS = 0U;
static constexpr uint32_t ALLOW_BINARY = 1U << 0U;
static constexpr uint32_t ALLOW_OCTAL = 1U << 1U;
static constexpr uint32_t ALLOW_HEX = 1U << 2U;
static constexpr uint32_t IGNORE_TRAILING = 1U << 3U;

static constexpr uint32_t MAX_PRECISION = 16;
static constexpr uint8_t BINARY = 2;
static constexpr uint8_t OCTAL = 8;
static constexpr uint8_t DECIMAL = 10;
static constexpr uint8_t HEXADECIMAL = 16;
static constexpr double HALF = 0.5;
static constexpr double EPSILON = std::numeric_limits<double>::epsilon();
static constexpr double MAX_SAFE_INTEGER = 9007199254740991;
static constexpr double MAX_VALUE = std::numeric_limits<double>::max();
static constexpr double MIN_VALUE = std::numeric_limits<double>::min();
static constexpr double POSITIVE_INFINITY = coretypes::TaggedValue::VALUE_INFINITY;
static constexpr double NAN_VALUE = coretypes::TaggedValue::VALUE_NAN;

// Helper defines for double
using coretypes::DOUBLE_EXPONENT_BIAS;
using coretypes::DOUBLE_EXPONENT_MASK;
using coretypes::DOUBLE_EXPONENT_MAX;
using coretypes::DOUBLE_EXPONENT_SIZE;
using coretypes::DOUBLE_HIDDEN_BIT;
using coretypes::DOUBLE_MAX_PRECISION;
using coretypes::DOUBLE_SIGN_MASK;
using coretypes::DOUBLE_SIGNIFICAND_MASK;
using coretypes::DOUBLE_SIGNIFICAND_SIZE;

using coretypes::INT16_BITS;
using coretypes::INT32_BITS;
using coretypes::INT64_BITS;
using coretypes::INT8_BITS;

class NumberHelper {
public:
    static bool IsFinite(JSTaggedValue number)
    {
        return number.IsInt() || (number.IsDouble() && std::isfinite(number.GetDouble()));
    }
    static bool IsNaN(JSTaggedValue number)
    {
        return number.IsDouble() && std::isnan(number.GetDouble());
    }
    static JSTaggedValue DoubleToString(JSThread *thread, double number, int radix);
    static bool IsEmptyString(const uint8_t *start, const uint8_t *end);
    static JSHandle<EcmaString> NumberToString(const JSThread *thread, JSTaggedValue number);
    static double TruncateDouble(double d);
    static double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags = NO_FLAGS);
    static int32_t DoubleToInt(double d, size_t bits);
    static int32_t DoubleInRangeInt32(double d);
    static JSTaggedValue Pow(double base, double exponent);
    static JSTaggedValue DoubleToExponential(JSThread *thread, double number, int digit);
    static JSTaggedValue DoubleToFixed(JSThread *thread, double number, int digit);
    static JSTaggedValue DoubleToPrecision(JSThread *thread, double number, int digit);
    static JSTaggedValue StringToDoubleWithRadix(const uint8_t *start, const uint8_t *end, int radix);
    static PandaString IntToString(int number);
    static JSTaggedValue StringToBigInt(JSThread *thread, JSHandle<JSTaggedValue> strVal);

private:
    static char Carry(char current, int radix);
    static double Strtod(const char *str, int exponent, uint8_t radix);
    static PandaString IntegerToString(double number, int radix);
    static PandaString DecimalsToString(double *numberInteger, double fraction, int radix, double delta);
    static bool IsNonspace(uint16_t c);
    static bool GotoNonspace(uint8_t **ptr, const uint8_t *end);
};

class JSDoubleOperator {
public:
    explicit JSDoubleOperator(double val)  // NOLINT(cppcoreguidelines-pro-type-member-init)
    {
        u_.fval = val;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }
    JSDoubleOperator()  // NOLINT(cppcoreguidelines-pro-type-member-init)
    {
        u_.uval = 0;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    int16_t GetExponent() const
    {
        return u_.bits.exponent;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    uint64_t GetSignificand() const
    {
        return u_.bits.significand;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    void SetExponent(int16_t val)
    {
        u_.bits.exponent = val;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    void SetSignificand(uint64_t val)
    {
        u_.bits.significand = val;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    void SetDouble(double val)
    {
        u_.fval = val;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

    double GetDouble() const
    {
        return u_.fval;  // NOLINT(cppcoreguidelines-pro-type-union-access)
    }

private:
    union {
        struct {
            uint64_t significand : DOUBLE_SIGNIFICAND_SIZE;
            uint16_t exponent : DOUBLE_EXPONENT_SIZE;
            int sign : 1;
        } bits __attribute__((packed));
        uint64_t uval;
        double fval;
    } __attribute__((may_alias, packed)) u_;
};

}  // namespace ark::ecmascript::base
#endif  // ECMASCRIPT_BASE_NUMBER_HELPER_H
